package com.factory.code.service.impl;

import com.factory.code.common.VelocityTemplateText;
import com.factory.code.common.helper.FilePathHelper;
import com.factory.code.common.util.VelocityUtil;
import com.factory.code.controller.param.GeneratorCodeParam;
import com.factory.code.controller.param.GeneratorDeployParam;
import com.factory.code.controller.param.TemplatePageParam;
import com.factory.code.controller.param.TemplateVerifyParam;
import com.factory.code.controller.vo.PageVO;
import com.factory.code.controller.vo.R;
import com.factory.code.lib.database.DatabaseBehaviorFactory;
import com.factory.code.lib.database.model.TableInfoModel;
import com.factory.code.repository.ProjectRepository;
import com.factory.code.repository.TemplateRepository;
import com.factory.code.repository.ent.ProjectENT;
import com.factory.code.repository.ent.TemplateENT;
import com.factory.code.service.TemplateCustomService;
import com.factory.code.service.TemplateService;
import com.factory.code.service.model.*;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.apache.velocity.VelocityContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import javax.persistence.criteria.Predicate;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

/**
 * @author
 */
@Service
public class TemplateServiceImpl implements TemplateService {
    private static final Logger logger = LoggerFactory.getLogger(TemplateServiceImpl.class);

    @Autowired
    private ProjectRepository projectRepository;
    @Autowired
    private TemplateRepository templateRepository;
    @Autowired
    private TemplateCustomService templateCustomService;


    @Override
    public List<TemplateENT> listAll() {
        return templateRepository.findAll();
    }

    @Override
    public List<TemplateENT> listAll(Integer projectId) {
        return templateRepository.findByProjectId(projectId);
    }

    @Override
    public TemplateENT getById(int id) {
        TemplateENT ent = templateRepository.getById(id);
        return ent;
    }

    @Autowired
    private DatabaseBehaviorFactory databaseBehaviorFactory;

    @SneakyThrows
    @Override
    public List<CodeFileModel> getTableCodeByNames(GeneratorCodeParam param) {

        Integer projectId = param.getProjectId();
        List<String> tableNames = param.getTableNames();

        ProjectENT projectENT = projectRepository.getById(projectId);
        List<TemplateENT> templateList = templateRepository.findByProjectId(projectId);

        List<CodeFileModel> list = new ArrayList<>();

        for (String tableName : tableNames) {
            for (TemplateENT templateENT : templateList) {
                if (param.getTemplateIds().contains(templateENT.getId())) {
                    CodeFileModel codeFile = getCodeFile(projectENT, templateENT, tableName,param.getDeploy());
                    list.add(codeFile);
                }
            }
        }
        return list;
    }

    @SneakyThrows
    @Override
    public String verifyTemplateContent(TemplateVerifyParam param) {
        ProjectENT projectENT = projectRepository.getById(param.getProjectId());
        String tableName = param.getTableName();
        if (StringUtils.isEmpty(tableName)) {
            TableInfoModel tableModel = databaseBehaviorFactory.getDatabaseBehavior(projectENT.getId()).getTableAll().get(0);
            tableName = tableModel.getTableName();
        }
        TemplateENT ent = new TemplateENT();
        ent.setContent(param.getContent());
        ent.setFileName(param.getFileName());
        ent.setFilePath(param.getFilePath());
        GeneratorDeployParam deploy=new GeneratorDeployParam(projectENT);
        CodeFileModel codeFile = getCodeFile(projectENT, ent, tableName,deploy);

        return codeFile.getContent();
    }

    private CodeFileModel getCodeFile(ProjectENT projectENT, TemplateENT templateENT, String tableName, GeneratorDeployParam deploy) {
        CodeTableModel tableModel = databaseBehaviorFactory.getDatabaseBehavior(projectENT.getId()).getTemplateModel(tableName, deploy);
        tableModel.setProjectENT(projectENT);
        tableModel.setTemplateENT(templateENT);

        VelocityContext context = new VelocityContext();
        context.put("context", tableModel.getContext());
        context.put("tableColumns", tableModel.getTableColumns());
        context.put("tableInfo", tableModel.getTableInfo());
        //获取自定义模板内容
        templateCustomService.setCustomTemplateContent(projectENT.getId(), context, tableModel);
        //文件名
        String fileName = VelocityUtil.generate(context, templateENT.getFileName());
        String filePath = VelocityUtil.generate(context, templateENT.getFilePath());
        //生成模板内容
        String content = VelocityUtil.generate(context, templateENT.getContent());
        //再次生成  可能有自定义模板内容
        content = VelocityUtil.generate(context, content);

        CodeFileModel codeFile = new CodeFileModel();
        codeFile.setFileName(fileName);
        codeFile.setContent(content);
        codeFile.setFilePath(filePath);
        return codeFile;
    }

    @Override
    public R update(TemplateENT templateENT) {
        Integer samples = projectRepository.getSamplesById(templateENT.getProjectId());
        if (samples != null && samples == 1) {
            return R.fail("示例不能操作");
        }
        if (!FilePathHelper.judgeFilePath(templateENT.getFilePath())) {
            return R.fail("文件路径格式正确");
        }
        String systemPathFormat = FilePathHelper.getSystemPathFormat(templateENT.getFilePath());

        templateENT.setFilePath(systemPathFormat);
        templateRepository.saveAndFlush(templateENT);
        return R.ok();
    }

    @Override
    public R add(TemplateENT templateENT) {
        Integer samples = projectRepository.getSamplesById(templateENT.getProjectId());
        if (samples != null && samples == 1) {
            return R.fail("示例不能操作");
        }
        if (!FilePathHelper.judgeFilePath(templateENT.getFilePath())) {
            return R.fail("文件路径格式正确");
        }
        String systemPathFormat = FilePathHelper.getSystemPathFormat(templateENT.getFilePath());
        templateENT.setFilePath(systemPathFormat);

        templateRepository.saveAndFlush(templateENT);
        return R.ok();
    }

    @Override
    public R delete(TemplateENT templateENT) {
        Integer samples = projectRepository.getSamplesById(templateENT.getProjectId());
        if (samples != null && samples == 1) {
            return R.fail("示例不能操作");
        }
        templateRepository.deleteById(templateENT.getId());
        return R.ok();
    }


    @Override
    public List<CodeVelocityModel> velocityTemplate(Integer projectId) {
        List<CodeVelocityModel> list = new ArrayList<>();

        CodeVelocityModel context = new CodeVelocityModel();
        context.setText("通用");
        context.setExpression("${context.}");
        List<CodeVelocityModel> contextList = getVelocityList("${context.%s}", CodeContextModel.class);
        context.setChildren(contextList);
        list.add(context);

        CodeVelocityModel tableColumns = new CodeVelocityModel();
        tableColumns.setText("表列信息  需要在 foreach 下使用");
        tableColumns.setExpression("#foreach($column in $tableColumns)         #end");
        List<CodeVelocityModel> velocityList = getVelocityList("${column.%s}", CodeTableColumnModel.class);
        tableColumns.setChildren(velocityList);
        list.add(tableColumns);
        list.add(getVelocityGrammar());
        if (projectId != null) {
            //获取自定义 模板 （可放代码块）
            list.add(templateCustomService.getCustomTemplate(projectId));
        }
        return list;
    }

    @Override
    public PageVO<TemplateENT> getPage(TemplatePageParam param) {
        Sort.Direction sort = Sort.Direction.DESC;
        Pageable pageable = PageRequest.of(param.getCurrentPage() - 1, param.getPageSize(), sort, "id");
        Specification specification = (Specification) (root, criteriaQuery, cb) -> {
            //增加筛选条件
            Predicate predicate = cb.conjunction();
            if (param.getProjectId() != null) {
                predicate.getExpressions().add(cb.equal(root.get("projectId"), param.getProjectId()));
            }
            if (StringUtils.isNotBlank(param.getName())) {
                predicate.getExpressions().add(cb.like(root.get("name"), "%" + param.getName() + "%"));
            }
            return predicate;
        };
        Page<TemplateENT> pageList = templateRepository.findAll(specification, pageable);
        PageVO vo = new PageVO();
        vo.setTotal(pageList.getTotalElements());
        vo.setRecords(pageList.getContent());
        return vo;
    }

    /**
     * 获取对象的   VelocityTemplateText 注解内容
     *
     * @param expr
     * @param c
     * @return
     */
    public List<CodeVelocityModel> getVelocityList(String expr, Class c) {
        List<CodeVelocityModel> list = new ArrayList<>();
        Field[] fields = c.getDeclaredFields();
        for (Field field : fields) {
            CodeVelocityModel model = new CodeVelocityModel();
            if (field.isAnnotationPresent(VelocityTemplateText.class)) {
                //获取注释内容
                VelocityTemplateText declaredAnnotation = field.getDeclaredAnnotation(VelocityTemplateText.class);
                String value = declaredAnnotation.value();
                String fieldName = field.getName();
                model.setText(value);
                model.setExpression(String.format(expr, fieldName));
                list.add(model);
            }
        }
        return list;
    }

    public CodeVelocityModel getVelocityGrammar() {
        CodeVelocityModel model = new CodeVelocityModel();
        model.setText("Velocity 常用语法");
        model.setExpression("");
        List<CodeVelocityModel> list = new ArrayList<>();
        list.add(new CodeVelocityModel("## 单行注释", "单行注释"));
        list.add(new CodeVelocityModel("#if(1=1) \n#else \n#end \n#foreach($a in [1,2,3]) \n${a} \n#end", "#   脚本语句 if else foreach "));
        list.add(new CodeVelocityModel("${name} \n${context.tableName} ", "$ 对象或变量  脚本语句 name context.tableName "));
        list.add(new CodeVelocityModel("${username}", "取 username 不存在返回：${username}  "));
        list.add(new CodeVelocityModel("$!{username}", "取 username 不存在返回： ‘’ "));
        list.add(new CodeVelocityModel("#if($name)\n#else \n#end ", "if else "));
        list.add(new CodeVelocityModel("#foreach($column in $tableColumns)\n  ${column.attrName}  \n#end", "for"));

        model.setChildren(list);
        return model;
    }


}
